package dao

import (
	"context"
	"errors"
	"time"

	"github.com/ego-component/egorm"
	"github.com/go-sql-driver/mysql"
	"gorm.io/gorm"
)

var ErrDataNotFound = gorm.ErrRecordNotFound

var ErrUserDuplicate = errors.New("用户邮箱或者手机号冲突")

//go:generate mockgen -source=user.go -package=daomocks -destination=mocks/user.mock.go UserDAO
type UserDAO interface {
	Insert(ctx context.Context, u User) (int64, error)
	UpdateNonZeroFields(ctx context.Context, u User) error
	FindByEmail(ctx context.Context, email string) (User, error)
	FindByID(ctx context.Context, id int64) (User, error)
	FindByIDs(ctx context.Context, ids []int64) ([]User, error)
	FindAll(ctx context.Context) ([]User, error)
	Count(ctx context.Context) (int64, error)
	FindByLimitOffset(ctx context.Context, limit int, offset int) ([]User, error)
}

type GORMUserDAO struct {
	db *egorm.Component
}

func NewGORMUserDAO(db *egorm.Component) UserDAO {
	return &GORMUserDAO{
		db: db,
	}
}

func (ud *GORMUserDAO) Insert(ctx context.Context, u User) (int64, error) {
	now := time.Now().UnixMilli()
	u.Ctime = now
	u.Utime = now
	err := ud.db.WithContext(ctx).Create(&u).Error
	me := new(mysql.MySQLError)
	if ok := errors.As(err, &me); ok {
		const uniqueIndexErrNo uint16 = 1062
		if me.Number == uniqueIndexErrNo {
			return 0, ErrUserDuplicate
		}
	}
	// 匿名登录需要返回用户 ID
	return u.ID, err
}

func (ud *GORMUserDAO) UpdateNonZeroFields(ctx context.Context, u User) error {
	// 使用 ID 来作为 WHERE 条件; 使用非零值来更新
	u.Utime = time.Now().UnixMilli()
	return ud.db.WithContext(ctx).Updates(&u).Error
}

func (ud *GORMUserDAO) FindByEmail(ctx context.Context, email string) (User, error) {
	var u User
	err := ud.db.WithContext(ctx).First(&u, "email = ?", email).Error
	return u, err
}

func (ud *GORMUserDAO) FindByID(ctx context.Context, id int64) (User, error) {
	var u User
	err := ud.db.WithContext(ctx).First(&u, "id = ?", id).Error
	return u, err
}

func (ud *GORMUserDAO) FindByIDs(ctx context.Context, ids []int64) ([]User, error) {
	var userList []User
	err := ud.db.WithContext(ctx).Where("id in (?)", ids).Find(&userList).Error
	return userList, err
}

func (ud *GORMUserDAO) FindAll(ctx context.Context) ([]User, error) {
	var userList []User
	err := ud.db.WithContext(ctx).Find(&userList).Error
	return userList, err
}

func (ud *GORMUserDAO) Count(ctx context.Context) (int64, error) {
	var count int64
	err := ud.db.Model(&User{}).WithContext(ctx).Count(&count).Error
	return count, err
}

func (ud *GORMUserDAO) FindByLimitOffset(ctx context.Context, limit, offset int) ([]User, error) {
	var userList []User
	err := ud.db.WithContext(ctx).Limit(limit).Offset(offset).Find(&userList).Error
	return userList, err
}

type User struct {
	ID       int64  `gorm:"primaryKey,autoIncrement,column:id"`
	Nickname string `gorm:"column:nickname"`
	Avatar   string `gorm:"column:avatar"`
	Token    string `gorm:"column:token"`
	Ctime    int64
	Utime    int64
}
